/*
 * @Description:
 * @Author: huangmin
 * @Email: 812949176@qq.com
 * @Date: 2023-02-05 15:21:42
 * @LastEditors: huangmin
 * @LastEditTime: 2023-03-02 17:10:25
 * @FilePath: /virtual-list/src/App.jsx
 */
/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable no-unused-vars */

import "./App.css";
import { useState, useRef, useEffect, useCallback } from "react";
import { Input, Radio, message, notification, Image as AntdImage } from "antd";
import mockData from "./mock";
import VirtualList from "./component/VirtualList";

const App = () => {
  const [currentPage, setPage] = useState(1); //currentPage表示目前是第几页，初始第1页
  const [listData, setListData] = useState([]); //listData表示列表，初始空数组
  const [loading, setLoading] = useState(false); //是否正在加载数据
  const [overScanCount, setOverScanCount] = useState(1);
  const [clientHeight, setClientHeight] = useState(
    document.body.clientHeight || 0
  );
  const [virtual, setVirtual] = useState(true);
  const [width, setWidth] = useState(null);
  const [type, setType] = useState(1);
  const containerRef = useRef();
  const virtualListRef = useRef();
  const pageSize = 30;
  const initSize = 10000; //首屏渲染数量
  const maxPage = 100; //最大页数
  const col = 4; //列数
  const randomData = (num) => {
    return new Promise((resolve, reject) => {
      const arr = Array.from(Array(num).keys(), (id) => {
        const base = listData.length;
        const r = Math.floor(Math.random() * 256);
        const g = Math.floor(Math.random() * 256);
        const b = Math.floor(Math.random() * 256);
        return {
          id: base + id,
          size: (Math.random() * (3 - 0.3) + 0.3).toFixed(2),
          color: `rgb(${r},${g},${b})`,
          bottomHeight: 50,
        };
      });
      setTimeout(() => {
        resolve(arr);
      }, 500);
    });
  };
  const getRandomImg = (num, base = listData.length) => {
    return fetch(`/api/shibes?count=${num}&urls=true&httpsUrls=false`)
      .then((res) => res.json())
      .then((data) => {
        return data.map((item, index) => {
          return {
            id: base + index,
            bottomHeight: 50,
            src: item,
          };
        });
      });
  };
  const getImageSize = (src) => {
    return new Promise((resolve, reject) => {
      let img = new Image();
      img.src = src;
      img.onload = img.onerror = (e) => {
        if (img.width && img.height) {
          resolve(img.width / img.height);
        } else {
          resolve(null);
        }
        img.onload = null;
        img.onerror = null;
      };
    });
  };
  const getDataWithSize = useCallback(
    async (num = pageSize, _, mockData) => {
      if (loading || currentPage > maxPage) {
        return;
      }
      if (mockData) {
        setLoading(true);
        await randomData(1);
        setListData(mockData);
        setPage(currentPage + 1);
        setLoading(false);
        return;
      }
      setLoading(true);
      const res = await randomData(num);
      setLoading(false);
      if (currentPage === 1) {
        setListData(res);
        setPage(currentPage + 1);
      } else {
        setPage(currentPage + 1);
        setListData((preData) => [...preData, ...res]);
      }
    },
    [currentPage, pageSize, loading]
  );
  /**
   * @description: 在服务端没办法返回图片尺寸时使用，支持同步或异步加载两种模式，弊端就是有n张图片，页面就需更新n次
   * @param {*} type 图片加载方式
   * @return {*}
   */
  const getDataWithoutSize = useCallback(
    async (num = pageSize, type = "async", initData) => {
      if (loading || currentPage > maxPage) {
        return;
      }
      if (type === "sync") {
        // 同步加载，获取到图片的尺寸->插入列表->获取下一张->...
        // 缺点：如果某张图片尺寸比较大，会阻塞渲染
        setLoading(true);
        let images;
        if (initData) {
          await getRandomImg(1);
          images = initData;
        } else {
          images = await getRandomImg(num);
        }
        for (let index = 0; index < images.length; index++) {
          const item = images[index];
          const size = await getImageSize(item.src);
          if (size !== null) {
            item.size = size;
            setListData((pre) => [...pre, item]);
          }
        }
        setLoading(false);
      } else if (type === "async") {
        // 同步加载，遍历注册onload事件->加载完成,插入列表->...
        // 缺点：这样列表的顺序可能不对，加载的快的先插入
        setLoading(true);
        const images = initData ? initData : await getRandomImg(num);
        let count = 0;
        for (let index = 0; index < images.length; index++) {
          const item = images[index];
          const img = new Image();
          img.src = item.src;
          // eslint-disable-next-line no-loop-func
          img.onload = img.onerror = () => {
            if (img.width && img.height) {
              const size = img.width / img.height;
              item.size = size;
              setListData((pre) => [...pre, item]);
            }
            count += 1;
            if (count === images.length) {
              setLoading(false);
            }
            img.onload = null;
            img.onerror = null;
          };
        }
      }
    },
    [currentPage, pageSize, loading]
  );

  const getData = getDataWithSize;
  // const getData = getDataWithoutSize;

  useEffect(() => {
    getData(initSize, "async");
    // getData(initSize,'async',mockData.slice(0,initSize));
  }, []);
  useEffect(() => {
    if (type === 0) {
      setClientHeight(document.body.clientHeight || 0);
    } else {
      setClientHeight(containerRef.current.clientHeight || 0);
    }
  }, [type]);

  const itemRender = useCallback((props) => {
    const { width, height, bottomHeight, id, color, src } = props;
    return (
      <div
        style={{
          background: color,
          boxShadow: "0px 2px 8px 0px rgb(0 0 0 / 9%)",
        }}
      >
        <div
          className="item-img"
          style={{ width, height: height }}
          onClick={() => {
            message.success(`这是第${id + 1}项`);
          }}
        >
          {/* 这是第{id + 1}项 */}
          {src && (
            <img
              src={src}
              alt=""
              width={width}
              height={height}
              fetchpriority="high"
            />
          )}
        </div>
        <div
          className="item-desc"
          style={{ height: bottomHeight }}
          onClick={() => {
            notification.success({
              message: "Notification Title",
              description:
                "This is the content of the notification. This is the content of the notification. This is the content of the notification.",
              className: "custom-class",
              style: {
                width: 300,
              },
            });
          }}
        >
          这是第{id + 1}项
        </div>
      </div>
    );
  }, []);

  const nav = (
    <div className="nav">
      <Input.Search
        enterButton
        placeholder="跳转到某一项"
        onSearch={(value) => {
          virtualListRef.current.scrollTo(value - 1);
        }}
        style={{
          width: 150,
        }}
      />
    </div>
  );
  // eslint-disable-next-line no-unused-vars
  const bottomRender = <div className="bottom-slot">----这是底线----</div>;
  return (
    <div className="App">
      <header className="App-header">虚拟列表demo</header>
      {nav}
      <div className="btn-g">
        <Radio.Group
          onChange={({ target }) => setType(target.value)}
          value={type}
        >
          <Radio.Button value={1}>容器内滚动</Radio.Button>
          <Radio.Button value={0}>基于窗口滚动</Radio.Button>
        </Radio.Group>
        <Radio.Group
          onChange={({ target }) => setOverScanCount(target.value)}
          value={overScanCount}
        >
          <Radio.Button value={-0.1}>渲染可视区域之内0.8屏</Radio.Button>
          <Radio.Button value={1}>渲染可视区域之外1屏</Radio.Button>
        </Radio.Group>
      </div>
      <div className="btn-g">
        <Radio.Group
          onChange={({ target }) => setWidth(target.value)}
          value={width}
        >
          <Radio.Button value={undefined}>列表项宽度自适应</Radio.Button>
          <Radio.Button value={200}>列表项200宽度</Radio.Button>
        </Radio.Group>
        <Radio.Group
          onChange={({ target }) => setVirtual(target.value)}
          value={virtual}
        >
          <Radio.Button value={true}>开启虚拟列表</Radio.Button>
          <Radio.Button value={false}>关闭虚拟列表</Radio.Button>
        </Radio.Group>
      </div>
      <div className="desc">可以尝试改变浏览器窗口大小</div>
      {/*辅助线  overScanCount配置为负数（-0.1）模拟观察滚动视窗范围*/}
      <div
        className="line-g"
        style={{
          height: "calc(100vh - 170px)",
          top: "170px",
        }}
      >
        <div
          className="line"
          style={{
            top: overScanCount * -1 * clientHeight,
            position: type === 1 ? "absolute" : "fixed",
          }}
        ></div>
        <div
          className="line"
          style={{
            bottom: overScanCount * -1 * clientHeight,
            position: type === 1 ? "absolute" : "fixed",
          }}
        ></div>
      </div>
      <div
        className="container"
        ref={containerRef}
        style={{
          height: type === 1 ? "calc(100vh - 170px)" : null,
          overflowY: type === 1 ? "auto" : null,
        }}
      >
        <VirtualList
          data={listData}
          itemRender={itemRender}
          colNum={col}
          loading={loading}
          virtual={virtual}
          itemWidth={width}
          overScanCount={overScanCount}
          onReachBottom={getData}
          scrollDom={type === 0 ? null : containerRef.current}
          ref={virtualListRef}
          reachBottomThreshold={50}
          bottomOptions={
            currentPage > maxPage
              ? {
                  height: 60,
                  render: bottomRender,
                }
              : undefined
          }
          dynamicProgramming={true}
        />

        {/* {测试正常布局渲染} */}
        {/* <div className="test">
            {listData.map((item, index) => (
              <div key={index} style={{marginBottom:20}}>{itemRender(item)}</div>
            ))}
          </div> */}
      </div>
    </div>
  );
};

export default App;
